# -*- coding: utf-8 -*-
"""
系统托盘管理器
提供托盘图标的创建和管理功能
"""

# 在任何tkinter导入之前设置环境
import sys
import os
from modules.tkinter_setup import setup_tcltk_environment
setup_tcltk_environment()

import tkinter as tk
from typing import Optional, Callable
import traceback

def _log(msg):
    """内部日志函数 - 安全版本，避免stderr未初始化时的错误"""
    try:
        # 首先尝试正常的stderr输出
        print(f"[TRAY] {msg}", file=sys.stderr, flush=True)
    except (OSError, ValueError):
        try:
            # 如果stderr失败，尝试stdout
            print(f"[TRAY] {msg}", file=sys.stdout, flush=True)
        except (OSError, ValueError):
            # 如果都失败，至少打印到控制台（最后的备选方案）
            try:
                print(f"[TRAY] {msg}")
            except Exception:
                # 如果所有方法都失败，静默处理，避免影响程序启动
                pass

# 默认值初始化
_WIN32_AVAILABLE = False
WM_RBUTTONUP = 517
WM_LBUTTONUP = 514
WM_LBUTTONDBLCLK = 515
WM_RBUTTONDOWN = 516
# 托盘通知相关常量
NIN_BALLOONUSERCLICK = 0x0406
NIN_BALLOONTIMEOUT = 0x0405
NIN_BALLOONSHOW = 0x0404
NIN_SELECT = 0x0400
NIN_KEYSELECT = 0x0401

# Windows 托盘所需
try:
    # 逐个导入，找出具体是哪个模块导入失败
    try:
        print(f"[TRAY] Python版本: {sys.version}", flush=True)
        print(f"[TRAY] sys.path: {sys.path[:3]}", flush=True)  # 只显示前3个路径，避免太长
        print(f"[TRAY] 当前目录: {os.getcwd()}", flush=True)
    except Exception:
        pass  # 静默处理导入阶段的日志问题
    
    import win32gui
    import win32con
    import win32api
    import win32gui_struct
    
    _WIN32_AVAILABLE = True
    # 明确定义消息常量，避免依赖win32con的定义
    WM_RBUTTONUP = win32con.WM_RBUTTONUP
    WM_LBUTTONUP = win32con.WM_LBUTTONUP
    WM_LBUTTONDBLCLK = win32con.WM_LBUTTONDBLCLK
    WM_RBUTTONDOWN = win32con.WM_RBUTTONDOWN
except Exception as e:
    _WIN32_AVAILABLE = False
    try:
        print(f"[TRAY] Windows API导入失败: {type(e).__name__}: {e}", flush=True)
        traceback.print_exc(flush=True)
    except Exception:
        # 如果连print都失败，说明环境完全不支持输出，静默处理
        pass


class TrayManager:
    """系统托盘管理器"""
    
    def __init__(self, root: tk.Tk, show_callback: Callable, hide_callback: Optional[Callable] = None, base_dir: Optional[str] = None, exit_callback: Optional[Callable] = None, settings_callback: Optional[Callable] = None):
        """
        初始化托盘管理器
        
        Args:
            root: Tk根窗口
            show_callback: 显示窗口的回调函数
            hide_callback: 隐藏窗口的回调函数（可选）
            base_dir: 程序基础目录（用于查找图标）
            exit_callback: 退出程序的回调函数（可选）
            settings_callback: 打开设置的回调函数（可选）
        """
        self.root = root
        self.show_callback = show_callback
        self.hide_callback = hide_callback
        self._is_hidden_to_tray = False
        self._base_dir = base_dir
        self._exit_callback = exit_callback
        self._settings_callback = settings_callback
        self._hwnd = None
        self._icon = None
        self._notify_id = None
        self._menu_ids = {}
        
        # 初始化日志
        _log(f"初始化托盘管理器, Windows API可用: {_WIN32_AVAILABLE}")
        
        # 创建托盘图标菜单（如果平台支持）
        self._setup_tray_menu()
        # 创建系统托盘图标（如果可用）- 常驻托盘
        if _WIN32_AVAILABLE:
            try:
                self._create_tray_icon()
                # 确保在Tkinter主循环中添加消息处理
                self._start_message_pump()
                _log("托盘图标已常驻系统托盘")
            except Exception as e:
                _log(f"ERROR creating tray icon: {e}")
                traceback.print_exc(file=sys.stderr)
    
    def _setup_tray_menu(self):
        """设置托盘菜单"""
        # Windows下使用win32gui创建托盘图标
        self._has_tray = _WIN32_AVAILABLE
        _log(f"托盘菜单设置完成, 支持状态: {self._has_tray}")
    
    def get_icon_image(self):
        """获取托盘图标图片（PIL Image），从内存加载"""
        try:
            from icon_embedded import get_icon_image
            return get_icon_image()
        except ImportError:
            # 如果嵌入模块不存在，尝试从文件加载
            if self._base_dir:
                import os
                from PIL import Image
                icon_path = os.path.join(self._base_dir, "res", "icon.png")
                if os.path.exists(icon_path):
                    return Image.open(icon_path)
        return None
    
    def hide_to_tray(self):
        """隐藏窗口到托盘"""
        if self._is_hidden_to_tray:
            _log("Already hidden, skipping")
            return
        
        self._is_hidden_to_tray = True
        _log(f"Hiding window to tray, window state: {self.root.state()}")
        # 最小化到托盘（隐藏窗口）
        try:
            self.root.withdraw()  # 隐藏窗口
            _log(f"Window withdrawn, state after: {self.root.state()}")
        except Exception as e:
            _log(f"ERROR hiding window: {e}")
            self._is_hidden_to_tray = False
    
    def show_from_tray(self):
        """从托盘显示窗口"""
        if not self._is_hidden_to_tray:
            _log("Not hidden, skipping show")
            return
        
        self._is_hidden_to_tray = False
        _log(f"Showing window from tray, window state: {self.root.state()}")
        # 显示窗口
        try:
            self.root.deiconify()  # 显示窗口
            self.root.lift()  # 置顶
            self.root.focus_force()  # 获取焦点
            _log(f"Window shown, state after: {self.root.state()}")
        except Exception as e:
            _log(f"ERROR showing window: {e}")
    
    def is_hidden(self) -> bool:
        """检查窗口是否隐藏到托盘"""
        return self._is_hidden_to_tray

    # ---- 托盘图标实现（Windows） ----
    def _create_tray_icon(self):
        if not _WIN32_AVAILABLE:
            return
        # 注册窗口类
        message_map = {
            win32con.WM_DESTROY: self._on_destroy,
            win32con.WM_COMMAND: self._on_command,
            win32con.WM_USER + 20: self._on_tray_notify,
            win32con.WM_USER + 1001: self._on_show_window_message,  # 处理来自其他实例的显示窗口消息
        }
        wc = win32gui.WNDCLASS()
        hinst = wc.hInstance = win32api.GetModuleHandle(None)
        wc.lpszClassName = "WindowSpyTrayClass"
        wc.lpfnWndProc = message_map
        try:
            class_atom = win32gui.RegisterClass(wc)
        except win32gui.error as e:
            # 已注册,继续使用
            _log(f"Window class already registered: {e}")
        # 使用WS_POPUP样式创建不可见的消息窗口，这是托盘图标所需的
        style = win32con.WS_POPUP
        self._hwnd = win32gui.CreateWindow(wc.lpszClassName, "WindowSpyTray", style,
                                           0, 0, 0, 0,  # 大小设为0x0
                                           0, 0, hinst, None)
        # 不调用UpdateWindow，避免窗口显示
        _log(f"Created invisible message window with hwnd: {self._hwnd}")
        
        # 载入图标
        icon_handle = self._load_hicon()
        if not icon_handle:
            # 使用默认应用图标
            icon_handle = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
        _log(f"Loaded icon handle: {icon_handle}")
        
        # 添加托盘图标 - 常驻托盘
        # NIF_ICON: 图标
        # NIF_MESSAGE: 消息处理
        # NIF_TIP: 提示文本
        # NIF_INFO: 气球提示（增强可见性）
        flags = win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP
        
        # 创建托盘图标数据结构
        nid = (self._hwnd, 0, flags, win32con.WM_USER + 20, icon_handle, "Windows Process Spy")
        try:
            # Shell_NotifyIcon 成功时返回 None,失败时抛出异常
            win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, nid)
            self._notify_id = nid
            _log("Tray icon created successfully and set to stay in system tray permanently")
            
            # 尝试显示一个快速的气球提示，增强用户感知
            try:
                # 更新托盘图标信息，添加气球提示
                nid_info = (self._hwnd, 0, win32gui.NIF_INFO | flags, 
                          win32con.WM_USER + 20, icon_handle, "Windows Process Spy", 
                          "程序已启动并常驻托盘", 5000, "系统提示")
                win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, nid_info)
                _log("Tray balloon notification displayed")
            except Exception as e:
                # 气球提示失败不影响核心功能
                _log(f"Optional balloon notification failed (not critical): {e}")
                
        except Exception as e:
            _log(f"ERROR adding tray icon: {e}")
            traceback.print_exc(file=sys.stderr)

    def _on_show_window_message(self, hwnd, msg, wparam, lparam):
        """处理来自其他实例的显示窗口消息"""
        _log(f"收到显示窗口消息: hwnd={hwnd}, msg={msg}")
        try:
            # 显示主窗口
            self.show_from_tray()
            _log("已响应显示窗口消息，主窗口已显示")
        except Exception as e:
            _log(f"处理显示窗口消息时出错: {e}")
        return 0

    def _destroy_tray_icon(self):
        if not _WIN32_AVAILABLE:
            return
        try:
            if self._notify_id:
                win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, self._notify_id)
                self._notify_id = None
            if self._hwnd:
                win32gui.DestroyWindow(self._hwnd)
                self._hwnd = None
        except Exception as e:
            _log(f"ERROR destroying tray icon: {e}")

    def _load_hicon(self):
        # 优先加载 res/icon.ico
        try:
            if self._base_dir:
                ico_path = os.path.join(self._base_dir, "res", "icon.ico")
                if os.path.exists(ico_path):
                    return win32gui.LoadImage(0, ico_path, win32con.IMAGE_ICON, 0, 0,
                                              win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE)
        except Exception as e:
            _log(f"Error loading icon from file: {e}")
        # 退回到 app 图标
        try:
            return win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
        except Exception as e:
            _log(f"Error loading default icon: {e}")
            return None

    def _on_tray_notify(self, hwnd, msg, wparam, lparam):
        _log(f"[EVENT TRACE] 收到托盘通知: hwnd={hwnd}, msg={msg}, wparam={wparam}, lparam={lparam}")
        
        # 关键：打印出所有可能的消息值，以便调试
        _log(f"[DEBUG] WM_RBUTTONUP={WM_RBUTTONUP}, WM_RBUTTONDOWN={WM_RBUTTONDOWN}, WM_LBUTTONUP={WM_LBUTTONUP}, WM_LBUTTONDBLCLK={WM_LBUTTONDBLCLK}")
        
        # 处理各种托盘事件
        if lparam == NIN_BALLOONUSERCLICK:
            _log("[EVENT] 托盘气泡点击")
        elif lparam == NIN_BALLOONTIMEOUT:
            _log("[EVENT] 托盘气泡超时")
        elif lparam == NIN_BALLOONSHOW:
            _log("[EVENT] 托盘气泡显示")
        elif lparam == NIN_SELECT:
            _log("[EVENT] 托盘图标选中")
        elif lparam == NIN_KEYSELECT:
            _log("[EVENT] 托盘图标键盘选中")
            
        # 左键单击 - 显示主窗口
        elif lparam == WM_LBUTTONUP:
            _log(f"[EVENT] 左键单击托盘图标 (WM_LBUTTONUP={WM_LBUTTONUP})")
            if self.show_callback:
                try:
                    _log("[ACTION] 执行show_callback")
                    self.show_callback()
                except Exception as e:
                    _log(f"[ERROR] show_callback失败: {e}")
                    traceback.print_exc(file=sys.stderr)
        # 右键弹出菜单 - 在 WM_RBUTTONUP 时显示
        elif lparam == WM_RBUTTONUP:
            _log(f"[EVENT] 右键释放托盘图标 (WM_RBUTTONUP={WM_RBUTTONUP}) - 关键事件!")
            try:
                _log("[ACTION] 开始显示右键菜单")
                self._show_tray_menu()
            except Exception as e:
                _log(f"[ERROR] 显示右键菜单失败: {e}")
                traceback.print_exc(file=sys.stderr)
        # 捕获右键按下事件作为备用
        elif lparam == WM_RBUTTONDOWN:
            _log(f"[EVENT] 右键按下托盘图标 (WM_RBUTTONDOWN={WM_RBUTTONDOWN}) - 备用事件!")
            # 也在这里尝试显示菜单，作为备用方案
            try:
                _log("[ACTION] 备用方案：显示右键菜单")
                self._show_tray_menu()
            except Exception as e:
                _log(f"[ERROR] 备用方案显示菜单失败: {e}")
                traceback.print_exc(file=sys.stderr)
        # 双击也显示窗口
        elif lparam == WM_LBUTTONDBLCLK:
            _log(f"[EVENT] 双击托盘图标 (WM_LBUTTONDBLCLK={WM_LBUTTONDBLCLK})")
            if self.show_callback:
                try:
                    _log("[ACTION] 执行show_callback")
                    self.show_callback()
                except Exception as e:
                    _log(f"[ERROR] show_callback失败: {e}")
        # 捕获所有其他通知消息 - 关键：这里可能漏掉了右键事件
        else:
            _log(f"[WARNING] 未处理的托盘通知类型: {lparam} - 这可能是右键事件!")
            # 额外的尝试：如果lparam在右键相关范围内，也尝试显示菜单
            if lparam in [516, 517, 513, 514, 515]:  # 可能的鼠标事件范围
                _log(f"[ACTION] 尝试处理可能的右键事件: {lparam}")
                try:
                    self._show_tray_menu()
                except Exception as e:
                    _log(f"[ERROR] 处理可能的右键事件失败: {e}")
        
        return True

    def _show_tray_menu(self):
        _log("[MENU TRACE] 开始显示右键菜单")
        try:
            _log("[MENU TRACE] 准备创建弹出菜单")
            
            # 确保在显示菜单前，菜单ID映射已准备好
            ID_SHOW = 1024
            ID_SETTINGS = 1025
            ID_EXIT = 1026
            
            self._menu_ids = {ID_SHOW: "show", ID_SETTINGS: "settings", ID_EXIT: "exit"}
            _log(f"[MENU TRACE] 菜单ID映射已设置: {self._menu_ids}")
            
            # 关键：确认win32gui和win32con可用
            if not hasattr(win32gui, 'CreatePopupMenu'):
                raise RuntimeError("win32gui.CreatePopupMenu 不可用")
            if not hasattr(win32con, 'MF_STRING'):
                raise RuntimeError("win32con.MF_STRING 不可用")
            
            # 创建菜单 - 关键步骤
            _log("[MENU TRACE] 调用CreatePopupMenu")
            menu = win32gui.CreatePopupMenu()
            _log(f"[MENU TRACE] 菜单句柄: {menu}")
            if not menu:
                raise RuntimeError("创建弹出菜单失败")
            
            # 添加菜单项 - 关键步骤
            _log("[MENU TRACE] 开始添加菜单项")
            try:
                win32gui.AppendMenu(menu, win32con.MF_STRING, ID_SHOW, "打开窗口")
                _log("[MENU TRACE] 添加 '打开窗口' 菜单项成功")
                win32gui.AppendMenu(menu, win32con.MF_STRING, ID_SETTINGS, "属性")
                _log("[MENU TRACE] 添加 '属性' 菜单项成功")
                win32gui.AppendMenu(menu, win32con.MF_SEPARATOR, 0, "")
                _log("[MENU TRACE] 添加分隔符成功")
                win32gui.AppendMenu(menu, win32con.MF_STRING, ID_EXIT, "退出")
                _log("[MENU TRACE] 添加 '退出' 菜单项成功")
            except Exception as e:
                _log(f"[ERROR] 添加菜单项失败: {e}")
                raise
            
            # 获取鼠标位置 - 关键步骤
            _log("[MENU TRACE] 获取鼠标位置")
            try:
                pos = win32gui.GetCursorPos()
                _log(f"[MENU TRACE] 鼠标位置: {pos}")
            except Exception as e:
                _log(f"[ERROR] 获取鼠标位置失败: {e}")
                # 使用默认位置作为备用
                pos = (0, 0)
                _log(f"[MENU TRACE] 使用默认位置: {pos}")
            
            # 确保窗口在前台 - 这是菜单显示的关键
            _log("[MENU TRACE] 设置窗口到前台")
            try:
                success = win32gui.SetForegroundWindow(self._hwnd)
                _log(f"[MENU TRACE] SetForegroundWindow 结果: {success}")
                if not success:
                    _log("[WARNING] 设置窗口到前台失败，尝试备用方法")
                    # 备用方法：先最小化再还原
                    try:
                        win32gui.CloseWindow(self._hwnd)
                        win32gui.OpenIcon(self._hwnd)
                        win32gui.SetForegroundWindow(self._hwnd)
                        _log("[MENU TRACE] 备用方法设置窗口到前台")
                    except Exception as e:
                        _log(f"[ERROR] 备用方法失败: {e}")
            except Exception as e:
                _log(f"[ERROR] SetForegroundWindow失败: {e}")
            
            # 显示菜单 - 最关键的步骤
            _log("[MENU TRACE] 准备调用 TrackPopupMenu")
            try:
                # 获取TPM常量值
                TPM_LEFTALIGN = getattr(win32con, 'TPM_LEFTALIGN', 0)
                TPM_RIGHTBUTTON = getattr(win32con, 'TPM_RIGHTBUTTON', 256)
                TPM_RETURNCMD = getattr(win32con, 'TPM_RETURNCMD', 2)
                _log(f"[MENU TRACE] TPM常量: LEFTALIGN={TPM_LEFTALIGN}, RIGHTBUTTON={TPM_RIGHTBUTTON}, RETURNCMD={TPM_RETURNCMD}")
                
                # 调用TrackPopupMenu - 这是显示菜单的核心API
                _log(f"[MENU TRACE] 调用TrackPopupMenu: pos={pos}, hwnd={self._hwnd}")
                result = win32gui.TrackPopupMenu(
                    menu,
                    TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
                    pos[0], pos[1],
                    0, self._hwnd, None
                )
                _log(f"[MENU TRACE] TrackPopupMenu 返回结果: {result}")
                
                # 确保菜单正常关闭的关键步骤
                _log("[MENU TRACE] 发送WM_NULL消息确保菜单正确关闭")
                try:
                    win32gui.PostMessage(self._hwnd, win32con.WM_NULL, 0, 0)
                    _log("[MENU TRACE] WM_NULL消息发送成功")
                except Exception as e:
                    _log(f"[ERROR] 发送WM_NULL消息失败: {e}")
                
                # 如果 TrackPopupMenu 返回了命令ID，可以直接处理
                if result in self._menu_ids:
                    action = self._menu_ids[result]
                    _log(f"[MENU TRACE] 直接处理菜单结果: {action}")
                    self._handle_menu_action(action)
                elif result != 0:
                    _log(f"[WARNING] 未知的菜单结果: {result}")
                else:
                    _log("[MENU TRACE] 用户取消了菜单操作")
                
            except Exception as e:
                _log(f"[ERROR] TrackPopupMenu调用失败: {e}")
                # 备用方案：尝试使用不同的参数
                try:
                    _log("[MENU TRACE] 尝试备用的TrackPopupMenu参数")
                    result = win32gui.TrackPopupMenu(
                        menu,
                        0,  # 使用默认参数
                        pos[0], pos[1],
                        0, self._hwnd, None
                    )
                    _log(f"[MENU TRACE] 备用TrackPopupMenu返回: {result}")
                except Exception as e2:
                    _log(f"[ERROR] 备用TrackPopupMenu也失败: {e2}")
                    raise
            
            _log("[MENU TRACE] 菜单显示完成")
                
        except Exception as e:
            _log(f"[CRITICAL ERROR] 创建或显示菜单失败: {e}")
            traceback.print_exc(file=sys.stderr)
    
    def _handle_menu_action(self, action):
        """处理菜单动作的辅助方法"""
        try:
            if action == "show":
                if self.show_callback:
                    _log("执行显示窗口操作")
                    self.show_callback()
            elif action == "settings":
                if self._settings_callback:
                    _log("执行打开设置操作")
                    self._settings_callback()
            elif action == "exit":
                if self._exit_callback:
                    _log("执行退出操作")
                    self._exit_callback()
        except Exception as e:
            _log(f"处理菜单动作 '{action}' 失败: {e}")

    def _on_command(self, hwnd, msg, wparam, lparam):
        try:
            cmd_id = win32api.LOWORD(wparam)
            _log(f"收到菜单命令: cmd_id={cmd_id}, wparam={wparam}, lparam={lparam}")
            if self._menu_ids:
                action = self._menu_ids.get(cmd_id)
                _log(f"执行菜单动作: {action}")
                if action:
                    self._handle_menu_action(action)
        except Exception as e:
            _log(f"处理菜单命令失败: {e}")
            traceback.print_exc(file=sys.stderr)
        return True

    def _on_destroy(self, hwnd, msg, wparam, lparam):
        try:
            _log("处理窗口销毁消息")
            if self._notify_id:
                win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, self._notify_id)
        except Exception as e:
            _log(f"处理销毁消息失败: {e}")
        return True

    def _start_message_pump(self):
        """启动消息泵来处理托盘图标的消息 - 增强版"""
        def pump_messages():
            """在Tkinter的事件循环中定期处理Win32消息"""
            try:
                # 处理所有待处理的消息
                if hasattr(self, '_hwnd') and self._hwnd and _WIN32_AVAILABLE:
                    # 使用循环确保所有消息都被处理
                    messages_processed = 0
                    # 使用PeekMessage和GetMessage的组合方式，确保不会漏掉消息
                    while True:
                        # PumpWaitingMessages返回False表示没有更多消息
                        if not win32gui.PumpWaitingMessages():
                            break
                        messages_processed += 1
                        # 避免处理过多消息导致Tkinter事件循环卡顿
                        if messages_processed > 100:  # 限制单次处理的消息数量
                            break
                    if messages_processed > 0:
                        _log(f"处理了 {messages_processed} 条消息")
            except Exception as e:
                _log(f"Error pumping messages: {e}")
                traceback.print_exc(file=sys.stderr)
            finally:
                # 继续定期检查消息，使用极短的间隔以确保右键菜单事件不会被错过
                if hasattr(self, '_hwnd') and self._hwnd and _WIN32_AVAILABLE:
                    # 使用10ms的间隔，这是Tkinter after方法的合理最小值
                    self.root.after(10, pump_messages)
        
        # 启动消息泵 - 使用线程池思想，确保消息处理不会阻塞主UI
        _log("启动消息泵 - 增强版本，优先处理右键事件")
        # 立即启动第一次消息处理
        self.root.after_idle(pump_messages)  # 使用after_idle确保在当前事件循环空闲时立即处理
        # 同时设置定时检查，双保险
        self.root.after(10, pump_messages)

    def dispose(self):
        """释放托盘图标资源（程序退出时调用）"""
        _log("释放托盘资源")
        self._destroy_tray_icon()

